#!/usr/bin/env python
# -*- coding: utf-8 -*-
#

import  os
import  time

import  storecell

class LRU:
    class Node:
        def __init__(self, prev, me):
            self.prev = prev
            self.me = me
            self.next = None

    def __init__(self, count):
        self.count = max(count, 2)
        self.d = {}
        self.first = None
        self.last = None

    def __contains__(self, obj):
        return obj in self.d

    def __getitem__(self, obj):
        return self.d[obj].me[1]

    def __setitem__(self, obj, val):
        if obj in self.d:
            del self[obj]
        nobj = self.Node(self.last, (obj, val))
        if self.first is None:
            self.first = nobj
        if self.last:
            self.last.next = nobj
        self.last = nobj
        self.d[obj] = nobj
        if len(self.d) > self.count:
            if self.first == self.last:
                self.first = None
                self.last = None
                return
            a = self.first
            a.next.prev = None
            self.first = a.next
            a.next = None
            del self.d[a.me[0]]
            del a

    def __delitem__(self, obj):
        nobj = self.d[obj]
        if nobj.prev:
            nobj.prev.next = nobj.next
        else:
            self.first = nobj.next
        if nobj.next:
            nobj.next.prev = nobj.prev
        else:
            self.last = nobj.prev
        del self.d[obj]

    def __iter__(self):
        cur = self.first
        while cur != None:
            cur2 = cur.next
            yield cur.me[1]
            cur = cur2
        raise StopIteration

    def iteritems(self):
        cur = self.first
        while cur != None:
            cur2 = cur.next
            yield cur.me
            cur = cur2
        raise StopIteration

    def iterkeys(self):
        return iter(self.d)

    def itervalues(self):
        for i,j in self.iteritems():
            yield j

    def keys(self):
        return [i for i,j in self.iteritems()]

    def values(self):
        return [j for i,j in self.iteritems()]

    def items(self):
        return [i for i in self.iteritems()]

    def __repr__(self):
        """representation. for debug only, do not affect LRU
        """
        x =''
        for k,v in self.d.items():
            if x :
                x = '%s, %s:%s' % (x, k.__repr__(), v.me[1].__repr__())
            else :
                x = '%s:%s' % (k.__repr__(), v.me[1].__repr__())
        return '{%s}' % x




class   StoreCache(object) :
    '''
    cache proxy for store object
    '''

    # local constant
    SC_NONE     = 0
    SC_INSERT   = 1
    SC_REPLACE  = 2
    SC_UPDATE   = 4
    SC_ERASE    = 8

    def __init__(self, store_path, read_cache_size=0, write_cache_size=0) :
        self.__store_path = store_path
        self.__read_cache_size = read_cache_size
        self.__write_cache_size = write_cache_size
        self.__cells = {}
        self.__cache = LRU( read_cache_size )

        self.__count_read = 0
        self.__count_read_hit_cache = 0
        self.__count_read_miss_cache = 0

        self.__out_buffer_type  = self.SC_NONE
        self.__out_buffer_count = 0
        self.__out_buffer       = {}

        #self.read_cache = {}
        #self.read_cache_size_used = 0
        #self.read_cache_key_list = []

        #self.write_cache = {}
        #self.write_cache_size_used = 0
        # __init__()

    def __del__(self) :
        self.__exit()
        # __del__()

    ####################################################################


    def    insert(self, cell_id, key, data) :
        '''
        insert or ignore
        '''
        if self.isWriteCached() :
            self.__enqueueOutBuffer(self.SC_INSERT, cell_id, key, data)
        else :
            cell = self.__getCell(cell_id)
            cell.insert(key, data)
        return self
        # insert()

    def    update(self, cell_id, key, data)    :
        '''
        update or ignore
        '''
        if self.isWriteCached() :
            self.__enqueueOutBuffer(self.SC_UPDATE, cell_id, key, data)
        else :
            cell = self.__getCell(cell_id)
            cell.update(key, data)
        return self
        # update()

    def    replace(self, cell_id, key, data) :
        '''
        update or insert
        '''
        if self.isWriteCached() :
            self.__enqueueOutBuffer(self.SC_REPLACE, cell_id, key, data)
        else :
            cell = self.__getCell(cell_id)
            cell.replace(key, data)
        return self
        # replace()

    def    erase(self, cell_id, key) :
        '''
        erase
        '''
        if self.isWriteCached() :
            self.__enqueueOutBuffer(self.SC_ERASE, cell_id, key)
        else :
            cell = self.__getCell(cell_id)
            cell.erase(key)
        return self
        # erase()

    def    find(self, cell_id, key) :
        '''
        find
        '''
        self.__count_read += 1

        if self.isReadCached() :
            if key in self.__cache :
                self.__count_read_hit_cache += 1
                return  self.__cache[key]

        # miss cache
        self.__count_read_miss_cache += 1

        # if write-cached, so we need flush first
        if self.isWriteCached() :
            self.__flush()

        cell = self.__getCell(cell_id)
        data = cell.find(key)

        if data :
            self.__cache[ key ] = data

        return  data
        # find()


    ##
    def setAttr(self, cell_id, key, name, value) :
        '''
        set attribute for a key
        '''
        cell = self.__getCell(cell_id)
        cell.setAttr(key, name, value)

        return  self

    def getAttr(self, cell_id, key, name) :
        cell = self.__getCell(cell_id)
        return cell.getAttr(key, name, value)


    ####################################################################

    def isWriteCached(self) :
        return  self.__write_cache_size > 0
        # isWriteCached()

    def isReadCached(self) :
        return  self.__read_cache_size > 0
        # isReadCached()

    def clearCacheStat(self) :
        self.__count_read = 0
        self.__count_read_hit_cache = 0
        self.__count_read_miss_cache = 0
        # clearCacheStat

    def statCache(self) :
        return (self.__count_read, self.__count_read_hit_cache, self.__count_read_miss_cache)
        # statCache()


    ####################################################################

    def __enqueueOutBuffer(self, content_type, cell_id, key, data = None) :
        '''
        enter a key-value pair to buffer
        '''
        if self.SC_NONE == self.__out_buffer_type :
            self.__out_buffer_type = content_type
        if content_type != self.__out_buffer_type :
            self.__flush()

        if cell_id in self.__out_buffer :
            if data is None : self.__out_buffer[ cell_id ].append( key )
            else : self.__out_buffer[ cell_id ].append( (key, data) )
        else :
            if data is None : self.__out_buffer[ cell_id ] = [ key ]
            else : self.__out_buffer[ cell_id ] = [ (key, data) ]

        self.__out_buffer_count += 1
        if self.__out_buffer_count >= self.__write_cache_size :
            self.__flush()

        return  self
        # __enqueue()

    def __flush(self) :
        '''
        flush content in write-buffer to storage
        '''
        if self.SC_NONE == self.__out_buffer_type :
            return  self

        if self.SC_INSERT == self.__out_buffer_type :
            for cell_id, kvs in self.__out_buffer.iteritems() :
                cell = self.__getCell( cell_id )
                cell.inserts( kvs )
        elif self.SC_UPDATE == self.__out_buffer_type :
            for cell_id, kvs in self.__out_buffer.iteritems() :
                cell = self.__getCell( cell_id )
                cell.updates( kvs )
        elif self.SC_REPLACE == self.__out_buffer_type :
            for cell_id, kvs in self.__out_buffer.iteritems() :
                cell = self.__getCell( cell_id )
                cell.replaces( kvs )
        elif self.SC_ERASE == self.__out_buffer_type :
            for cell_id, keys in self.__out_buffer.iteritems() :
                cell = self.__getCell( cell_id )
                cell.erases( keys )
        else :
            raise RuntimeError, 'Unknown out buffer type : %d' % (self.__out_buffer_type, )

        self.__out_buffer_type  = self.SC_NONE
        self.__out_buffer_count = 0
        self.__out_buffer       = {}

        return  self
        # __flush()


    def __getCell(self, cell_id) :
        '''
        get storage cell which should holding key
        '''
        if cell_id in self.__cells :
            # FOUND, return it
            return  self.__cells[ cell_id ]

        # NOT FOUND, construct a new cell
        cell_store_path = self.__store_path + os.sep + str(cell_id)
        if not os.path.isdir( cell_store_path ) :
            os.makedirs(cell_store_path, 0777)
        cell = storecell.create( cell_store_path )
        self.__cells[ cell_id ] = cell
        return  cell
        # __getCell()

    def __exit(self) :
        self.__cells = {}
        # __exit()


